/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package sirtet.tetris.gui.fancy;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedList;
import javax.swing.Timer;
import sirtet.Piece;
import sirtet.event.BoardBlocksEliminatedEvent;
import sirtet.event.BoardEvent;
import sirtet.event.BoardPieceFixedEvent;
import sirtet.tetris.BattlingTetrisBoard;
import sirtet.tetris.BoardAttackListener;
import sirtet.tetris.BoardAttackedEvent;
import sirtet.tetris.TetrisBoard;
import sirtet.tetris.TetrisPiece;
import sirtet.tetris.gui.TetrisBoardPanel;

/**
 *
 * @author guangwei.zhu
 */
public class FancyBoardPanel extends TetrisBoardPanel implements BoardAttackListener {

    private int BOARD_WIDTH, BOARD_HEIGHT;
    private LinkedList<Object[]> attackQueue = new LinkedList<Object[]>();
    private Object[][] colorMap;
    private int currentColor;
    private FancyResource resource = new FancyResource();
    private static Color TRANSPARENT = new Color(0, 0, 0, 0);
    private int cautionLevel = 0;     // 1 - caution ; 2 - warning
    private boolean blinkShow = false;
    private boolean suppressFix = false;
    private Color bgColor = Color.DARK_GRAY;
    private boolean started = false;
    private boolean gameover = false;
    private int pieceColor = 0;
    private Timer blinkTimer = new Timer(500, new ActionListener() {

        public void actionPerformed(ActionEvent arg0) {
            blinkShow = !blinkShow;
            repaint();
        }
    });
    private Timer refreshTimer = new Timer(50, new ActionListener() {

        public void actionPerformed(ActionEvent arg0) {
            repaint();
        }
    });

    private void initColorMap() {
        if (colorMap == null) {
            colorMap = new Object[BOARD_HEIGHT][BOARD_WIDTH];
        }

        for (int i = 0; i < BOARD_HEIGHT; i++) {
            for (int j = 0; j < BOARD_WIDTH; j++) {
                colorMap[i][j] = 0;
            }
        }
    }

    public FancyBoardPanel(TetrisBoard board) {
        super(board);
        BOARD_WIDTH = board.getWidth();
        BOARD_HEIGHT = board.getHeight();

        initColorMap();
        if (board instanceof BattlingTetrisBoard) {
            ((BattlingTetrisBoard) board).addBoardAttackListener(this);
        }

        this.setBackground(Color.DARK_GRAY);

        currentColor = FancyResource.generateRandomColorIndex();
        blinkTimer.start();
        refreshTimer.start();

        cautionLevel = 0;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void boardBlocksEliminated(BoardBlocksEliminatedEvent evt) {
        Object[] eliminatedLines = (Object[]) (evt.eliminatedBlocks);
        int i, j, line;

        // Fix the current piece first
        Point pos = new Point(board.getCurrentPosition());
        Piece piece = board.getCurrentPiece();

        // Calculate the fixed piece offest

        for (i = 0; i < piece.getHeight(); i++) {
            for (j = 0; j < piece.getWidth(); j++) {
                if (piece.getElement(j, i) != piece.getEmptyElement()) {
                    if (pos.y + i >= BOARD_HEIGHT) {
                        continue;
                    }
                    colorMap[pos.y + i][pos.x + j] = currentColor;
                }
            }
        }

        for (Object oline : eliminatedLines) {
            line = ((Integer) oline).intValue();
            for (i = line; i > 0; i--) {
                System.arraycopy(colorMap[i - 1], 0, colorMap[i], 0, BOARD_WIDTH);
            }
            for (j = 0; j < BOARD_WIDTH; j++) {
                colorMap[0][j] = TetrisPiece.EMPTY;
            }
        }

        suppressFix = true;
    }

    @Override
    public void boardPieceFixed(BoardPieceFixedEvent evt) {

        int count = attackQueue.size();
        int i, j;
        Point pos;

        if (!suppressFix) {
            // Fix the current piece first
            pos = new Point(evt.position);
            Piece piece = evt.piece;

            // Calculate the fixed piece offest

            for (i = 0; i < piece.getHeight(); i++) {
                for (j = 0; j < piece.getWidth(); j++) {
                    if (piece.getElement(j, i) != piece.getEmptyElement()) {
                        if (pos.y + i >= BOARD_HEIGHT) {
                            continue;
                        }
                        colorMap[pos.y + i][pos.x + j] = currentColor;
                    }
                }
            }
        }
        suppressFix = false;
        synchronized (this) {
            // If there are sooo many lines in buffer...
            while (count >= BOARD_HEIGHT) {
                attackQueue.pollFirst();    // Get rid of some
                count = attackQueue.size();
            }

            // Shift up existing blocks
            for (i = 0; i < BOARD_HEIGHT - count; i++) {
                colorMap[i] = colorMap[i + count];
            }

            // Stuff attacked blocks at the bottom
            for (i = BOARD_HEIGHT - count; i < BOARD_HEIGHT; i++) {
                colorMap[i] = attackQueue.pollFirst();
            }
        }


        // Change the next piece color
        pieceColor += 1;
        pieceColor %= 4;
        if (pieceColor + 1 == FancyResource.COLOR_GRAY) {
            currentColor = FancyResource.COLOR_YELLOW;
        } else {
            currentColor = pieceColor + 1;
        }
        cautionLevel = 0;

        this.repaint();
    }

    @Override
    public void BoardAttacked(BoardAttackedEvent evt) {
        Object[][] attackBlocks = evt.blocks;
        Object[] copyLine;
        int i;

        for (Object[] line : attackBlocks) {
            copyLine = new Object[line.length];
            for (i = 0; i < line.length; i++) {
                if ((Integer) line[i] != TetrisPiece.EMPTY) {
                    copyLine[i] = FancyResource.COLOR_GRAY;      // Attack blocks are shown gray
                } else {
                    copyLine[i] = FancyResource.COLOR_NULL;
                }
            }
            attackQueue.addLast(copyLine);
        }

        // Show caution
        if (attackQueue.size() > 0) {
            if (attackQueue.size() > 2) {
                cautionLevel = 2;
            } else {
                cautionLevel = 1;
            }
        }
    }

    private int pileHeight() {
        for (int i = 0; i < BOARD_HEIGHT; i++) {
            for (int j = 0; j < BOARD_WIDTH; j++) {
                if ((Integer) colorMap[i][j] != FancyResource.COLOR_NULL) {
                    return BOARD_HEIGHT - i;
                }
            }
        }
        return 0;
    }

    @Override
    public void boardStarted(BoardEvent evt) {
        super.boardStarted(evt);
    }

    @Override
    public void boardGameOver(BoardEvent evt) {
        super.boardGameOver(evt);
        gameover = true;
        repaint();
    }

    @Override
    public void boardChanged(BoardEvent evt) {
        super.boardChanged(evt);
        started = true;
    }

    @Override
    public void paintComponent(Graphics g) {
        Point origin = this.getOrigin();
        Dimension blockSize = this.getBlockSize();
        Graphics2D g2d = (Graphics2D) g;
        Composite cmp = null;
        int ph;     // pile height

        int i, j;
        Point pos = new Point();
        if (!started) {
            bgColor = Color.white;
        } else if (gameover) {
            bgColor = new Color(0, 0, 32);
            cmp = g2d.getComposite();
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.05f));
        } else {
            ph = pileHeight();
            bgColor = new Color(128 * ph / 30, 32, 32, 128);
            if (ph > 25) {
                cautionLevel = 2;
            } else if (ph > 20) {
                cautionLevel = 1;
            }
        }

        // Draw fixed blocks
        for (i = 0; i < BOARD_HEIGHT; i++) {
            for (j = 0; j < BOARD_WIDTH; j++) {
                pos.x = j * blockSize.width + origin.x;
                pos.y = i * blockSize.height + origin.y;
                renderBlock(g, colorMap[i][j], pos, blockSize);
            }
        }

        // Draw falling piece
        if (board == null || board.getCurrentPiece() == null) {
            return;
        }

        TetrisPiece piece;

        // Draw falling piece
        piece = (TetrisPiece) board.getCurrentPiece();
        for (i = 0; i < piece.getHeight(); i++) {
            for (j = 0; j < piece.getWidth(); j++) {
                if (piece.getElement(j, i) != piece.getEmptyElement()) {
                    pos = board.getCurrentPosition();
                    pos.translate(j, i);
                    pos.x *= blockSize.width;
                    pos.y *= blockSize.height;
                    pos.translate(origin.x, origin.y);
                    if ((Integer) piece.getElement(j, i) == TetrisPiece.EMPTY) {
                        renderBlock(g, FancyResource.COLOR_NULL, pos, blockSize);
                    } else {
                        renderBlock(g, currentColor, pos, blockSize);
                    }
                }
            }
        }

        if (gameover) {
            Image cautionImage = resource.getGameoverImage();
            Point cautionPos = new Point((this.getWidth() - cautionImage.getWidth(null)) / 2,
                    (this.getHeight() - cautionImage.getHeight(null)) / 2);
            if (cmp != null) {
                g2d.setComposite(cmp);
            }
            g.drawImage(cautionImage, cautionPos.x, cautionPos.y, TRANSPARENT, this);
        } else if (blinkShow && cautionLevel > 0) {
            Image cautionImage = resource.getCautionImage(cautionLevel);
            Point cautionPos = new Point((this.getWidth() - cautionImage.getWidth(null)) / 2,
                    (this.getHeight() - cautionImage.getHeight(null)) / 2);
            g.drawImage(cautionImage, cautionPos.x, cautionPos.y, TRANSPARENT, this);
        }
    }

    @Override
    protected void renderBlock(Graphics g, Object block,
            Point point, Dimension size) {
        if ((Integer) block != FancyResource.COLOR_NULL) {
            if (!gameover) {
                g.drawImage(resource.getColorImage((Integer) block), point.x, point.y, size.width, size.height, bgColor, null);
            } else {
                g.drawImage(resource.getColorImage(FancyResource.COLOR_GRAY), point.x, point.y, size.width, size.height, bgColor, null);
            }
        } else {
            g.setColor(bgColor);
            g.fillRect(point.x, point.y, size.width, size.height);
        }
    }
}
